package com.gxar.marketdelivery.application.query;

import com.gxar.marketdelivery.application.command.DeliveryBuilder;
import com.gxar.marketdelivery.application.command.DeliveryTraceIdGenerator;
import com.gxar.marketdelivery.application.context.DeliveryContext;
import com.gxar.marketdelivery.domain.enums.DeliveryStatus;
import com.gxar.marketdelivery.domain.factory.MaterialFactory;
import com.gxar.marketdelivery.interfaces.web.param.DeliveryParam;
import com.gxar.marketdelivery.interfaces.web.result.CDeliveryPlanResult;
import com.gxar.marketdelivery.interfaces.web.result.CDeliveryResSlotResult;
import com.gxar.marketdelivery.shared.model.DeliveryPlanModel;
import com.gxar.marketdelivery.shared.model.DeliveryResSlotModel;
import com.gxar.marketdelivery.shared.model.DeliveryRuleModel;
import com.gxar.marketdelivery.shared.utils.CollectionUtils;
import com.gxar.marketdelivery.shared.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author nice
 * @version 1.0
 * @created 2023/8/2.
 */
@Slf4j
@Component
public class DeliveryHandler extends RuleHandler {

    @Autowired
    protected DeliveryBuilder deliveryBuilder;
    @Autowired
    protected MaterialFactory materialFactory;

    public CDeliveryResSlotResult getResSlot(DeliveryParam deliveryRequest, DeliveryResSlotModel resSlotModel) {
        DeliveryContext context = prepareContext(deliveryRequest);
        List<CDeliveryResSlotResult> resSlotInfos = multiGetResSlot(context, Collections.singletonList(resSlotModel));
        return CollectionUtils.getFirst(resSlotInfos);
    }

    public List<CDeliveryResSlotResult> multiGetResSlot(DeliveryParam deliveryRequest,
            List<DeliveryResSlotModel> resSlotModels) {
        DeliveryContext context = prepareContext(deliveryRequest);
        return multiGetResSlot(context, resSlotModels);
    }

    /**
     * 多个资源位批量获取
     *
     * @param resSlotModels
     * @return
     */
    private List<CDeliveryResSlotResult> multiGetResSlot(DeliveryContext context,
            List<DeliveryResSlotModel> resSlotModels) {
        /*
         * 1.构造上下文
         * 2.单个计划过滤器
         *  In:DeliveryPlan
         *  Out:DeliveryPlan
         * 3.批量计划过滤器
         *  In:List<DeliveryPlan>
         *  Out:List<DeliveryPlan>
         * 4.返回值装饰
         *  In:List<DeliveryResSlotInfo>
         *  Out:List<DeliveryResSlotInfo>
         */
        //1.构造上下文
        List<DeliveryPlanModel> planModels = resSlotModels.stream().map(DeliveryResSlotModel::getPlanModels)
                .filter(Objects::nonNull).flatMap(List::stream).filter(planModel -> {
                    if (BooleanUtils.isTrue(context.getPreview()) && Objects.nonNull(context.getPreviewConf())) {
                        return true;
                    }
                    return DeliveryStatus.ONLINE.equalByCode(planModel.getPlanStatus());
                }).collect(Collectors.toList());

        List<CDeliveryResSlotResult> resultInfos;
        try {
            Map<Long, DeliveryPlanModel> planModelMap = CollectionUtils.toMap(planModels, DeliveryPlanModel::getId);
            //2.单个计划过滤器
            List<DeliveryRuleModel> ruleModels = planModels.stream().map(DeliveryPlanModel::getDeliveryRuleModel)
                    .collect(Collectors.toList());
            ruleModels = filterByMatcher(context, ruleModels);
            //3.批量过滤
            ruleModels = filterByFilter(context, ruleModels);
            planModels = ruleModels.stream().map((ruleModel) -> planModelMap.get(ruleModel.getTargetId()))
                    .filter(Objects::nonNull).collect(Collectors.toList());
            Map<Long, List<DeliveryPlanModel>> filteredResSlotPlanMap = CollectionUtils.toListMap(planModels,
                    DeliveryPlanModel::getResSlotId);
            //过滤出真正要返回的资源位
            resSlotModels = resSlotModels.stream()
                    .peek(resSlotModel -> sortAndLimitPlanModels(resSlotModel, filteredResSlotPlanMap))
                    .collect(Collectors.toList());
            planModels = resSlotModels.stream().map(DeliveryResSlotModel::getPlanModels).filter(Objects::nonNull)
                    .flatMap(List::stream).collect(Collectors.toList());
            List<CDeliveryPlanResult> planInfos = deliveryBuilder.buildPlan(context, planModels);
            Map<Long, List<CDeliveryPlanResult>> resSlotPlanMap = CollectionUtils.toListMap(planInfos,
                    e -> Long.parseLong(e.getResSlotId()));
            //资源位物料
            Map<Long, String> slotMaterialAndAbtestMap = materialFactory.parseMaterial(context, resSlotModels);
            resultInfos = resSlotModels.stream()
                    .map(resSlotModel -> convertAndFillInfo(resSlotModel, resSlotPlanMap, slotMaterialAndAbtestMap))
                    .filter(Objects::nonNull).collect(Collectors.toList());
        } catch (Exception e) {
            throw e;
        }
        return resultInfos;
    }

    private void sortAndLimitPlanModels(DeliveryResSlotModel resSlotModel,
            Map<Long, List<DeliveryPlanModel>> filteredResSlotPlanMap) {
        List<DeliveryPlanModel> filteredPlanModels = filteredResSlotPlanMap.get(resSlotModel.getId());
        if (CollectionUtils.isEmpty(filteredPlanModels)) {
            resSlotModel.setPlanModels(null);
        } else {
            int resLimitCount = ObjectUtils.defaultIfNull(resSlotModel.getResLimitCount(), filteredPlanModels.size());
            filteredPlanModels = filteredPlanModels.stream()
                    .sorted(Comparator.comparing(DeliveryPlanModel::getPriority)).limit(resLimitCount)
                    .collect(Collectors.toList());
            resSlotModel.setPlanModels(filteredPlanModels);
        }
    }

    protected DeliveryContext prepareContext(DeliveryParam request) {
        DeliveryContext context = new DeliveryContext();
        return context;
    }

    private CDeliveryResSlotResult convertAndFillInfo(DeliveryResSlotModel resSlotModel,
            Map<Long, List<CDeliveryPlanResult>> resSlotPlanMap, Map<Long, String> slotMaterialAndAbtestMap) {
        CDeliveryResSlotResult resSlotInfo = new CDeliveryResSlotResult();
        resSlotInfo.setId(String.valueOf(resSlotModel.getId()));
        resSlotInfo.setResSlotKey(resSlotModel.getResSlotKey());
        resSlotInfo.setResSlotName(resSlotModel.getResSlotName());
        resSlotInfo.setDescription(resSlotModel.getDescription());
        resSlotInfo.setPlanResults(resSlotPlanMap.get(resSlotModel.getId()));
        if (CollectionUtils.isEmpty(resSlotInfo.getPlanResults())) {
            resSlotInfo.setSlotTraceId(DeliveryTraceIdGenerator.genDynamicSlotTraceId(resSlotModel.getId()));
        } else {
            resSlotInfo.setSlotTraceId(DeliveryTraceIdGenerator.genStaticSlotTraceId(resSlotModel.getId()));
        }

        if (resSlotModel.getMaterialModel() != null) {
            String material = slotMaterialAndAbtestMap.get(resSlotModel.getId());
            if (StringUtils.isNotBlank(material)) {
                resSlotInfo.setMaterialJson(material);
            }
        }
        return resSlotInfo;
    }
}
